问题
在31条说过在使用enum时最好不要企图通过ordinal()方法以获取枚举元素在枚举类中的索引顺序,然后在其他代码中依赖这种顺序,这样的代码是不安全的。比如代码中数组的索引依赖于enum的序数:
public class Herb { public enum Type { ANNUAL, BIENNIAL, PERENNTAL } // private static final Herb Type = null; private final String name; private final Type type; Herb(String name, Type type) { this.name = name; this.type = type; } @Override public String toString() { return name; } // 数组索引顺序与枚举元素的序数强依赖 Herb[] garden = { new Herb("1", Type.ANNUAL), new Herb("2", Type.BIENNIAL), new Herb("3", Type.PERENNTAL) }; Set<Herb>[] herbsByType = (Set<Herb>[])new Set[Herb.Type.values().length]; for(int i = 0;i<herbsByType.length;i++){ herbsByType[i] = new HashSet<Herb>(); } for(Herb h:garden){ herbsByType[h.type.ordinal()].add(h); } }}
上例中的代码旨在将Herb类型按照enum Type顺序进行排序,在构建的garden数组是与枚举元素的序数强依赖的,加入Type中的枚举元素一不小心调换了顺序,而garden并没有改变,再按照枚举元素在枚举类中的序数从garden数组中获取数据自然而然会出错。
那么如何解决?
解决
实际上,上例是想通过枚举值作为数组的索引,是枚举到数组值之间的映射关系,枚举充当着“索引键”的关系,在这样的使用场景下,可以采用EnumMap,该类专门用于枚举键,更加安全可靠。
EnumMap的介绍
EnumMap是一个与枚举类一起使用的Map实现,EnumMap中所有key都必须是单个枚举类的枚举值。创建EnumMap时必须显式或隐式指定它对应的枚举类。
EnumMap在内部以数组形式保存,所以这种实现形式非常紧凑、高效。
EnumMap根据key的自然顺序(即枚举值在枚举类中的定义顺序)来维护来维护key-value对的次序。当程序通过keySet()、entrySet()、values()等方法来遍历EnumMap时即可看到这种顺序。
EnumMap不允许使用null作为key值,但允许使用null作为value。如果试图使用null作为key将抛出NullPointerException异常。如果仅仅只是查询是否包含值为null的key、或者仅仅只是使用删除值为null的key,都不会抛出异常。
将上例由EnumMap改写
Herb[] garden = { new Herb("1", Type.ANNUAL), new Herb("2", Type.BIENNIAL), new Herb("3", Type.PERENNTAL) }; Map<Herb.Type, Set<Herb>> herbsByType = new EnumMap<Herb.Type,Set<Herb>>(Herb.Type.class); for(Herb.Type t:Herb.Type.values()){ herbsByType.put(t, new HashSet<Herb>()); } for(Herb h:garden) herbsByType.get(h.type).add(h); System.out.println(herbsByType);
可以看出由EnumMap改造后,程序更加简洁,也更加安全。总之,试图通过ordinal()方法,利用枚举元素在枚举中的位置做逻辑处理的,这种代码一般都是不可取和不安全的。
总结
最好不要用序数来索引数组 。如果你所表示的这种关系是多维的 , 就使用 EnumMap<…,EnumMap<…>> 。代码中一般情况下都不使用 Enum.ordinal , 即使要用也是因为特殊情况。